package renderable;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.SwingUtilities;

import rita.settings.Language;
import rita.ui.component.MessageDialog;
import rita.ui.component.MessageDialog.MessageType;
import rita.widget.SourceCode;

import workspace.Workspace;
import workspace.WorkspaceEvent;
import codeblocks.Block;
import codeblocks.BlockConnector;
import codeblocks.BlockGenus;
import codeblocks.BlockStub;
import codeblockutil.LabelWidget;

/**
 * BlockLabel is a region on a block in which text is displayed and possibly
 * edited. The location and font of a BlockLabel is specified in BlockShape and
 * the text displayed is specified by a Block, BlockLabel is the gateway for
 * text to be rendered and modified.
 * 
 * The key nature of a BlockLabel is that it is a JLabel when being viewed, and
 * a JTextField when it is being edited.
 * 
 * During mouse move, entered and exited events a white border is toggled around
 * the label for particular blocks. This white border helps to suggest editable
 * labels for blocks that have this enabled.
 */
public class BlockLabel implements MouseListener, MouseMotionListener,
		KeyListener {
	/** Enum for the differnt types of labels in codeblocks */
	public enum Type {
		NAME_LABEL, PAGE_LABEL, PORT_LABEL, DATA_LABEL
	}

	public final static Font blockFontSmall_Bold = new Font("Arial", Font.BOLD,
			7);
	public final static Font blockFontMedium_Bold = new Font("Arial",
			Font.BOLD, 10);
	public final static Font blockFontLarge_Bold = new Font("Arial", Font.BOLD,
			12);
	public final static Font blockFontSmall_Plain = new Font("Arial",
			Font.PLAIN, 7);
	public final static Font blockFontMedium_Plain = new Font("Arial",
			Font.PLAIN, 10);
	public final static Font blockFontLarge_Plain = new Font("Arial",
			Font.PLAIN, 12);

	private LabelWidget widget;
	/** These keys inputs are delegated back to renderable block */
	private final char[] validOperators = { '-', '+', '/', '*', '=', '<', '>',
			'x', 'X' };
	private Long blockID;
	private BlockLabel.Type labelType;
	private double zoom = 1.0;

	/**
	 * BlockLabel Constructor NOTE: A true boolean passed into the isEditable
	 * parameter does not necessarily make the label editable, but a false
	 * boolean will make the label uneditable.
	 */
	public BlockLabel(String initLabelText, BlockLabel.Type labelType,
			boolean isEditable, Color tooltipBackground) {
		// call other constructor
		this(initLabelText, labelType, isEditable, -1, false, tooltipBackground,false); //,false
	}

	public BlockLabel(String initLabelText, BlockLabel.Type labelType,
			boolean isEditable, long blockID, boolean hasComboPopup,
			Color tooltipBackground,boolean isLoading) { //,boolean isLoading
		if (Block.NULL.equals(blockID)) {
			throw new RuntimeException(
					"May not pass a null block instance as the parent of a block label");
		}
		if (initLabelText == null)
			initLabelText = "";
		this.blockID = blockID;
		this.labelType = labelType;
		widget = new LabelWidget(initLabelText, Block.getBlock(blockID)
				.getColor().darker(), tooltipBackground) {
			private static final long serialVersionUID = 328149080424L;

			protected void fireTextChanged(String text) {
				textChanged(text);
			}

			protected void fireGenusChanged(String genus) {
				genusChanged(genus);
			}

			protected void fireDimensionsChanged(Dimension value) {
				dimensionsChanged(value);
			}

			protected boolean isTextValid(String text) {
				return textValid(text);
			}
		};
		widget.setNumeric(Block.getBlock(this.blockID).getGenusName().equals(
				"number"));

		// Only editable if the isEditable parameter was true, the label is
		// either a Block's name or
		// socket label, the block can edit labels, and the block is not in the
		// factory.
		widget
				.setEditable(isEditable
						&& (labelType == BlockLabel.Type.NAME_LABEL || labelType == BlockLabel.Type.PORT_LABEL)
						&& Block.getBlock(blockID).isLabelEditable()
						&& !(RenderableBlock.getRenderableBlock(blockID) instanceof FactoryRenderableBlock));
		if (labelType == null || labelType.equals(BlockLabel.Type.NAME_LABEL)) {
			widget.setFont(BlockLabel.blockFontLarge_Bold);
		} else if (labelType.equals(BlockLabel.Type.PAGE_LABEL)) {
			widget.setFont(BlockLabel.blockFontMedium_Bold);
		} else if (labelType.equals(BlockLabel.Type.PORT_LABEL)) {
			widget.setFont(BlockLabel.blockFontMedium_Bold);
		} else if (labelType.equals(BlockLabel.Type.DATA_LABEL)) {
			widget.setFont(BlockLabel.blockFontMedium_Bold);
		}
		if (Block.getBlock(blockID).hasSiblings()) {
			// Map<String, String> siblings = new HashMap<String, String>();
			List<String> siblingsNames = Block.getBlock(blockID)
					.getSiblingsList();
			String[][] siblings = new String[siblingsNames.size() + 1][2];
			siblings[0] = new String[] {
					Block.getBlock(blockID).getGenusName(),
					Block.getBlock(blockID).getInitialLabel() };
			for (int i = 0; i < siblingsNames.size(); i++) {
				siblings[i + 1] = new String[] {
						siblingsNames.get(i),
						BlockGenus.getGenusWithName(siblingsNames.get(i))
								.getInitialLabel() };
			}
			widget.setSiblings(hasComboPopup
					&& Block.getBlock(blockID).hasSiblings(), siblings);
		}

		widget.addMouseListenerToLabel(this);
		widget.addMouseMotionListenerToLabel(this);
		widget.addKeyListenerToTextField(this);

		// set initial text
		//se pregunta si es el momento de la carga porque sino, pierde los sockets!
//		if (!isLoading)
			widget.updateLabelText(initLabelText,isLoading);
		// add and show the textLabel initially
		widget.setEditingState(false);
	}

	public void setZoomLevel(double newZoom) {
		this.zoom = newZoom;
		widget.setZoomLevel(newZoom);
	}

	public int getAbstractWidth() {
		if (widget.hasSiblings()) {
			return (int) (widget.getWidth() / zoom)
					- LabelWidget.DROP_DOWN_MENU_WIDTH;
		} else {
			return (int) (widget.getWidth() / zoom);
		}
	}

	public int getAbstractHeight() {
		return (int) (widget.getHeight() / zoom);
	}

	public int getPixelWidth() {
		return widget.getWidth();
	}

	public int getPixelHeight() {
		return widget.getHeight();
	}

	public Point getPixelLocation() {
		return widget.getLocation();
	}

	public void setEditable(boolean isEditable) {
		widget.setEditable(isEditable);
	}

	public boolean editingText() {
		return widget.editingText();
	}

	public void highlightText() {
		widget.highlightText();
	}

	public void setPixelLocation(int x, int y) {
		widget.setLocation(x, y);
	}

	public String getText() {
		return widget.getText();
	}

	public void setText(String text) {
			widget.setText(text);
	}

	public void setText(boolean text) {
		widget.setText(text);
	}

	public void setText(double text) {
		widget.setText(text);
	}

	public void setToolTipText(String text) {
		widget.assignToolTipToLabel(text);
	}

	public void showMenuIcon(boolean show) {
		widget.showMenuIcon(show);
	}

	public JComponent getJComponent() {
		return widget;
	}

	public void setEditingState(boolean editing) {
		widget.setEditingState(editing);
	}

	protected int rescale(int x) {
		return (int) (x * zoom);
	}

	protected int rescale(double x) {
		return (int) (x * zoom);
	}

	protected int descale(int x) {
		return (int) (x / zoom);
	}

	protected int descale(double x) {
		return (int) (x / zoom);
	}

	/** returns the blockID for this BlockLabel */
	Long getBlockID() {
		return blockID;
	}

	protected void textChanged(String text) {
		String oldLabel=Block.getBlock(blockID).getBlockLabel();
		if ((this.labelType.equals(BlockLabel.Type.NAME_LABEL) || this.labelType
				.equals(BlockLabel.Type.PORT_LABEL))
				&& Block.getBlock(blockID).isLabelEditable()) {
			if (this.labelType.equals(BlockLabel.Type.NAME_LABEL)
					) {
				if (!Block.getBlock(blockID).isDataBlock() && text.contains(" ")){
					 new MessageDialog(Language.get("textChanged.blank"),MessageType.ERROR);
					 return;
				}
				boolean paramOK=validateParams(text);
				if (!paramOK){
					 new MessageDialog(Language.get("textChanged.param"),MessageType.ERROR);
					 return;
				}
				
				Block.getBlock(blockID).setBlockLabel(text);
			}
			BlockConnector plug = Block.getBlock(blockID).getPlug();
			// Check if we're connected to a block. If we are and the the block
			// we're connected to
			// has stubs, update them.
			if (plug != null && plug.getBlockID() != Block.NULL) {
				if (Block.getBlock(plug.getBlockID()) != null) {
					if (Block.getBlock(plug.getBlockID())
							.isProcedureDeclBlock()
							&& Block.getBlock(plug.getBlockID()).hasStubs()) {
						// Blocks already store their socket names when saved so
						// it is not necessary
						// nor desired to call the connectors changed event
						// again.
						if (Block.getBlock(blockID).getGenusName().contains(
								"proc-param")) {
							BlockStub.parentConnectorsLabelChanged(
									plug.getBlockID(), widget.getLabelBeforeEdit(), Block.getBlock(blockID));
						} else if (RenderableBlock.getRenderableBlock(
								plug.getBlockID()).isLoading()) {
							BlockStub
									.parentConnectorsChanged(plug.getBlockID());
						}
					}
				}
			}
			RenderableBlock rb = RenderableBlock.getRenderableBlock(blockID);
			if (rb != null) {
				Workspace.getInstance().notifyListeners(new WorkspaceEvent(rb.getParentWidget(), blockID, WorkspaceEvent.BLOCK_RENAMED));
			}
		}
	}

	private boolean validateParams(String text) {
		Block block = Block.getBlock(this.blockID);
		if (block.getGenusName().contains("proc-param")) {
			// this is the custom method
			Block method = Block.getBlock(block.getPlug().getBlockID());
			if (method != null) {
				for (BlockConnector bc : method.getSockets()) {
					if (bc.getBlockID()!=Block.NULL && !bc.getBlockID().equals(block.getBlockID())) {
						if (Block.getBlock(bc.getBlockID())!=null && Block.getBlock(bc.getBlockID()).getBlockLabel()
								.equals(text))
							return false;
					}
				}
			}

		}
		return true;
	}

	protected void genusChanged(String genus) {
		if (widget.hasSiblings()) {
			Block oldBlock = Block.getBlock(blockID);
			oldBlock.changeGenusTo(genus);
			RenderableBlock rb = RenderableBlock.getRenderableBlock(blockID);
			rb.repaintBlock();
			Workspace.getInstance().notifyListeners(
					new WorkspaceEvent(rb.getParentWidget(), blockID,
							WorkspaceEvent.BLOCK_GENUS_CHANGED));
		}
	}

	protected void dimensionsChanged(Dimension value) {
		if (RenderableBlock.getRenderableBlock(blockID) != null) {
			RenderableBlock.getRenderableBlock(blockID).repaintBlock();
		}
	}

	protected boolean textValid(String text) {
		return !text.equals("") && BlockUtilities.isLabelValid(blockID, text);
	}

	public void mouseClicked(MouseEvent e) {
		if (!((e.getClickCount() == 1) && widget.isEditable())) {
			RenderableBlock.getRenderableBlock(blockID).processMouseEvent(
					SwingUtilities.convertMouseEvent(widget, e, widget
							.getParent()));
		}
	}

	public void mousePressed(MouseEvent e) {
		if (widget.getParent() != null
				&& widget.getParent() instanceof MouseListener) {
			RenderableBlock.getRenderableBlock(blockID).processMouseEvent(
					SwingUtilities.convertMouseEvent(widget, e, widget
							.getParent()));
		}
	}

	public void mouseReleased(MouseEvent e) {
		if (widget.getParent() != null
				&& widget.getParent() instanceof MouseListener) {
			RenderableBlock.getRenderableBlock(blockID).processMouseEvent(
					SwingUtilities.convertMouseEvent(widget, e, widget
							.getParent()));
		}
	}

	public void mouseEntered(MouseEvent e) {
		if (widget.getParent() != null
				&& widget.getParent() instanceof MouseListener) {
			RenderableBlock.getRenderableBlock(blockID).processMouseEvent(
					SwingUtilities.convertMouseEvent(widget, e, widget
							.getParent()));
		}
	}

	public void mouseExited(MouseEvent e) {
		if (widget.getParent() != null
				&& widget.getParent() instanceof MouseListener) {
			RenderableBlock.getRenderableBlock(blockID).processMouseEvent(
					SwingUtilities.convertMouseEvent(widget, e, widget
							.getParent()));
		}
	}

	public void mouseDragged(MouseEvent e) {
		if (widget.getParent() != null
				&& widget.getParent() instanceof MouseMotionListener) {
			((MouseMotionListener) widget.getParent())
					.mouseDragged(SwingUtilities.convertMouseEvent(widget, e,
							widget.getParent()));
		}
	}

	public void mouseMoved(MouseEvent e) {
	}

	public void keyPressed(KeyEvent e) {
		switch (e.getKeyCode()) {
		case KeyEvent.VK_ESCAPE:
			RenderableBlock.getRenderableBlock(blockID).requestFocus();
			return;
		case KeyEvent.VK_ENTER:
			RenderableBlock.getRenderableBlock(blockID).requestFocus();
			return;
		case KeyEvent.VK_TAB:
			RenderableBlock.getRenderableBlock(blockID).processKeyPressed(e);
			return;
		}
		if (Block.getBlock(this.blockID).getGenusName().equals("number")) {
			if (e.getKeyChar() == '-' && widget.canProcessNegativeSign()) {
				return;
			}
			for (char c : validOperators) {
				if (e.getKeyChar() == c) {
					RenderableBlock.getRenderableBlock(blockID).processKeyPressed(e);
					return;
				}
			}
		}
	}

	public void keyReleased(KeyEvent e) {
	}

	public void keyTyped(KeyEvent e) {
	}
}